# Copyright 2014 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

import argparse
import logging
import multiprocessing
import os
import sys

from expect_tests import handle_list, handle_debug, handle_train, handle_test
from expect_tests.cover import CoverageContext
from expect_tests.pipeline import result_loop
from expect_tests.tempdir import TempDir


HANDLERS = {
  'list': handle_list.ListHandler,
  'debug': handle_debug.DebugHandler,
  'train': handle_train.TrainHandler,
  'test': handle_test.TestHandler,
}


def _parse_args(args):
  args = args or sys.argv[1:]

  parser = argparse.ArgumentParser()
  subparsers = parser.add_subparsers(
      title='Mode (default "test")', dest='mode',
      help='See `[mode] --help` for more options.')

  for k, h in HANDLERS.iteritems():
    doc = h.__doc__
    if doc:
      doc = doc[0].lower() + doc[1:]
    sp = subparsers.add_parser(k, help=doc)
    h.add_options(sp)

    mg = sp.add_mutually_exclusive_group()
    mg.add_argument(
        '--quiet', action='store_true',
        help='be quiet (only print failures)')
    mg.add_argument(
        '--verbose', action='store_true', help='be verbose')

    if not h.SKIP_RUNLOOP:
      sp.add_argument(
          '--jobs', metavar='N', type=int,
          default=multiprocessing.cpu_count(),
          help='run N jobs in parallel (default %(default)s)')

      sp.add_argument(
          '--force-coverage', '--force_coverage', action='store_true',
          help='Enable coverage report even when specifying a test filter.')

    sp.add_argument(
        '--no-coverage', '--no_coverage', action='store_true',
        help='Disable coverage report.')

    sp.add_argument(
        '--test-list', '--test_list', metavar='FILE',
        help='take the list of test globs from the FILE (use "-" for stdin)'
    ).completer = lambda **_: []

    sp.add_argument(
        '--html-report', '--html_report', metavar='DIR',
        help='directory to write html report (default: disabled)'
    ).completer = lambda **_: []

    sp.add_argument(
        '--leak-temp', '--leak_temp', action='store_true',
        help='leak temporary directory for debugging (default: False)'
    )

    sp.add_argument(
        'test_glob', nargs='*', help=(
            'paths to look for tests in. It can be a directory containing '
            'packages, a package, a file inside a package or a test inside a '
            'file (append the name of the test to the path using a colon)')
    )

  opts = parser.parse_args(args)

  if not hasattr(opts, 'jobs'):
    opts.jobs = 0
  elif opts.jobs < 1:
    parser.error('--jobs was less than 1')

  if opts.test_list:
    fh = sys.stdin if opts.test_list == '-' else open(opts.test_list, 'rb')
    with fh as tl:
      opts.test_glob += [l.strip() for l in tl.readlines()]

  opts.handler = HANDLERS[opts.mode]

  if opts.mode == 'debug':
    opts.jobs = 1
    # Duplicate stdin because it's closed by Process, and pdb needs it.
    opts.handler.stdin = os.fdopen(os.dup(sys.stdin.fileno()))

  # Coverage activation. Coverage doesn't make sense in debug or train.
  if opts.no_coverage:
    opts.coverage = False
  else:
    opts.coverage = opts.mode not in ('debug', 'train')
    if hasattr(opts, 'force_coverage') and opts.force_coverage:
      opts.coverage = True

  del opts.test_list
  del opts.mode
  return opts


# TODO(iannucci): have Test determine cover_branches
def main(cover_branches=False, args=None):
  """Entry point for tests using expect_tests.

  Example:
  >>> import expect_tests

  >>> def happy_fn(val):
  >>>   # Usually you would return data which is the result of some
  >>>   # deterministic computation.
  >>>   return expect_tests.Result({'neet': '%s string value' % val})

  >>> @expect_tests.test_generator
  >>> def gen():
  >>>   yield expect_tests.Test(
  >>>     __package__ + '.happy',
  >>>     expect_tests.FuncCall(happy_fn, 'happy'))

  >>> if __name__ == '__main__':
  >>>   expect_tests.main(cover_branches=True)

  @param cover_branches: Include branch coverage data (rather than just line
                         coverage)
  @param test_modules: Modules containing expect_tests generators and/or
                       unittests. Defaults to all modules in the cwd.
  @param args: Commandline args (starting at argv[1])
  """
  try:
    opts = _parse_args(args)
    skip_loop = opts.handler.SKIP_RUNLOOP

    cover_ctx = CoverageContext(cover_branches, opts.html_report,
                                not skip_loop)

    # This is a quick hack to disable rude modules from implicitly configuring
    # basic logging at import-time.
    map(logging.getLogger().removeHandler, logging.getLogger().handlers)

    with TempDir(opts.leak_temp):
      error, killed = result_loop(cover_ctx.create_subprocess_context(), opts)

    cover_ctx.cleanup()
    if (not killed and
        (not opts.test_glob or
        opts.coverage)):
      if not cover_ctx.report(opts.verbose):
        sys.exit(2)

    sys.exit(error or killed)
  except KeyboardInterrupt:
    pass
